<?PHP
include_once 'logic/UserCenter.php';
include_once 'logic/Room.php';
include_once 'logic/User.php';
include_once 'logic/Seat.php';
include_once 'logic/Table.php';
include_once 'logic/Response.php';
include_once 'logic/UUID.php';
include_once 'logic/CardFactory.php';
include_once 'logic/card/CardConfig.php';
/**
 * patServer
 * PHP socket server base class
 * Events that can be handled:
 *   * onStart
 *   * onConnect
 *   * onConnectionRefused
 *   * onClose
 *   * onShutdown
 *   * onReceiveData
 *
 * @version 1.1
 * @author   Stephan Schmidt <schst@php-tools.de>
 * @package patServer
 */
class patServer{
	/**
	 * information about the project
	 * @var array $systemVars
	 */
	var $systemVars   = array(

    "appName"   => "patServer",
    "appVersion"   => "1.1",
    "author"   => array("Stephan Schmidt <schst@php-tools.de>", )
	);

	/**
	 * port to listen
	 * @var integer   $port
	 */
	var $port   = 10000;

	/**
	 * domain to bind to
	 * @var string $domain
	 */
	var $domain   = "localhost";

	/**
	 * maximum amount of clients
	 * @var integer $maxClients
	 */
	var $maxClients = -1;

	/**
	 * buffer size for socket_read
	 * @var integer $readBufferSize
	 */
	var $readBufferSize   = 128;

	/**
	 * end character for socket_read
	 * @var integer $readEndCharacter
	 */
	var $readEndCharacter = "\n";

	/**
	 * maximum of backlog in queue
	 * @var integer $maxQueue
	 */
	var $maxQueue = 500;

	/**
	 * debug mode
	 * @var boolean $debug
	 */
	var $debug   = true;

	/**
	 * debug mode
	 * @var string $debugMode
	 */
	var $debugMode = "text";

	/**
	 * debug destination (filename or stdout)
	 * @var string $debugDest
	 */
	var $debugDest = "stdout";

	/**
	 * empty array, used for socket_select
	 * @var array $null
	 */
	var $null   = array();

	/**
	 * all file descriptors are stored here
	 * @var array $clientFD
	 */
	var $clientFD = array();

	/**
	 * needed to store client information
	 * @var array $clientInfo
	 */
	var $clientInfo = array();

	/**
	 * needed to store server information
	 * @var array $serverInfo
	 */
	var $serverInfo = array();

	/**
	 * amount of clients
	 * @var integer   $clients
	 */
	var $clients = 0;
	 
	/*========================== edit by riidt start =============================*/

	var $userCenter = null;




	/*========================== edit by riidt end =============================*/

	/**
	 * create a new socket server
	 *
	 * @access public
	 * @param string   $domain   domain to bind to
	 * @param integer   $port   port to listen to
	 */
	function patServer( $domain = "localhost", $port = 10000 ){
		$this->domain = $domain;
		$this->port   = $port;

		$this->serverInfo["domain"]         = $domain;
		$this->serverInfo["port"]         = $port;
		$this->serverInfo["servername"]     = $this->systemVars["appName"];
		$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];

		set_time_limit( 0 );
	}

	/**
	 * set maximum amount of simultaneous connections
	 *
	 * @access public
	 * @param int $maxClients
	 */
	function setMaxClients( $maxClients ){
		$this->maxClients = $maxClients;
	}

	/**
	 * set debug mode
	 *
	 * @access public
	 * @param mixed $debug [text|htmlfalse]
	 * @param string $dest destination of debug message (stdout to output or filename if log should be written)
	 */
	function setDebugMode( $debug, $dest = "stdout" ){
		if( $debug === false ){
			$this->debug = false;
			return true;
		}

		$this->debug   = true;
		$this->debugMode = $debug;
		$this->debugDest = $dest;
	}

	/**
	 * start the server
	 *
	 * @access public
	 * @param int $maxClients
	 */
	function start(){
		/*========================== edit by riidt �� =============================*/
		$this->userCenter = UserCenter::getInstance();
		$this->userCenter->debug();

		$this->userCenter->initRoom();



		/*========================== edit by riidt �� =============================*/

		$this->initFD = @socket_create( AF_INET, SOCK_STREAM, 0 );
		if( !$this->initFD )
		die( "patServer: Could not create socket." );

		// adress may be reused
		socket_setopt( $this->initFD, SOL_SOCKET, SO_REUSEADDR, 1 );

		// bind the socket
		if(!@socket_bind( $this->initFD, $this->domain, $this->port ) ){
			@socket_close( $this->initFD );
			die( "patServer: Could not bind socket to ".$this->domain." on port ".$this->port." ( ".$this->getLastSocketError( $this->initFd )." )." );
		}

		// listen on selected port
		if(!@socket_listen( $this->initFD, $this->maxQueue ) )
		die( "patServer: Could not listen ( ".$this->getLastSocketError( $this->initFd )." )." );

		$this->sendDebugMessage( "Listening on port ".$this->port.". Server started at ".date( "H:i:s", time() ) );

		// this allows the shutdown function to check whether the server is already shut down
		$GLOBALS["_patServerStatus"] = "running";
		// this ensures that the server will be sutdown correctly
		register_shutdown_function( array( $this, "shutdown" ) );

		if( method_exists( $this, "onStart" ) )
		$this->onStart();

		$this->serverInfo["started"] = time();
		$this->serverInfo["status"]   = "running";

		while( true ){
			$readFDs = array();
			array_push( $readFDs, $this->initFD );

			// fetch all clients that are awaiting connections
			for( $i = 0; $i < count( $this->clientFD ); $i++ )
			if( isset( $this->clientFD[$i] ) )
			array_push( $readFDs, $this->clientFD[$i] );

			// block and wait for data or new connection
			$ready = @socket_select( $readFDs, $this->null, $this->null, NULL );

			if( $ready === false ){
				$this->sendDebugMessage( "socket_select failed." );
				$this->shutdown();
			}

			// check for new connection
			if( in_array( $this->initFD, $readFDs ) ){
				$newClient = $this->acceptConnection( $this->initFD );

				// check for maximum amount of connections
				if( $this->maxClients > 0 ){
					if( $this->clients > $this->maxClients ){
						$this->sendDebugMessage( "Too many connections." );

						if( method_exists( $this, "onConnectionRefused" ) )
						$this->onConnectionRefused( $newClient );

						$this->closeConnection( $newClient );
					}
				}

				if( --$ready <= 0 )
				continue;
			}

			// check all clients for incoming data
			for( $i = 0; $i < count( $this->clientFD ); $i++ ){
				if( !isset( $this->clientFD[$i] ) )
				continue;

				if( in_array( $this->clientFD[$i], $readFDs ) ){
					$data = $this->readFromSocket( $i );

					// empty data => connection was closed
					if( !$data ){
						$this->sendDebugMessage( "Connection closed by peer" );
						$this->closeConnection( $i );
					}else{
						$this->sendDebugMessage( "Received ".trim( $data )." from ".$i );

						if( method_exists( $this, "onReceiveData" ) )
						$this->onReceiveData( $i, $data );
					}
				}
			}
		}
	}

	/**
	 * read from a socket
	 *
	 * @access private
	 * @param integer $clientId internal id of the client to read from
	 * @return string $data   data that was read
	 */
	function readFromSocket( $clientId ){
		// start with empty string
		$data   = "";

		// read data from socket
		while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) ){
			$data .= $buf;

			$endString = substr( $buf, - strlen( $this->readEndCharacter ) );
			if( $endString == $this->readEndCharacter )
			break;
			if( $buf == NULL )
			break;
		}

		if( $buf === false )
		$this->sendDebugMessage( "Could not read from client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );

		return substr($data, 0, -1);
	}

	/**
	 * accept a new connection
	 *
	 * @access public
	 * @param resource &$socket socket that received the new connection
	 * @return int    $clientID internal ID of the client
	 */
	function acceptConnection( &$socket ){
		for( $i = 0 ; $i <= count( $this->clientFD ); $i++ ){
			if( !isset( $this->clientFD[$i] ) || $this->clientFD[$i] == NULL ){
				$this->clientFD[$i] = socket_accept( $socket );
				socket_setopt( $this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1 );
				$peer_host = "";
				$peer_port = "";
				socket_getpeername( $this->clientFD[$i], $peer_host, $peer_port );
				$this->clientInfo[$i] = array(
              "host"   => $peer_host,
              "port"   => $peer_port,
              "connectOn" => time()
				);
				$this->clients++;

				$this->sendDebugMessage( "New connection ( ".$i." ) from ".$peer_host." on port ".$peer_port );

				if( method_exists( $this, "onConnect" ) )
				$this->onConnect( $i );
				return $i;
			}
		}
	}

	/**
	 * check, whether a client is still connected
	 *
	 * @access public
	 * @param integer $id client id
	 * @return boolean $connected true if client is connected, false otherwise
	 */
	function isConnected( $id ){
		if( !isset( $this->clientFD[$id] ) )
		return false;
		return true;
	}

	/**
	 * close connection to a client
	 *
	 * @access public
	 * @param int $clientID internal ID of the client
	 */
	function closeConnection( $id ){
		if( !isset( $this->clientFD[$id] ) )
		return false;

		if( method_exists( $this, "onClose" ) )
		$this->onClose( $id );

		$this->sendDebugMessage( "Closed connection ( ".$id." ) from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"] );

		@socket_close( $this->clientFD[$id] );
		$this->clientFD[$id] = NULL;
		unset( $this->clientInfo[$id] );
		$this->clients--;
		 
	}

	/**
	 * shutdown server
	 *
	 * @access public
	 */
	function shutDown(){
		if( $GLOBALS["_patServerStatus"] != "running" )
		exit;
		$GLOBALS["_patServerStatus"] = "stopped";

		if( method_exists( $this, "onShutdown" ) )
		$this->onShutdown();

		$maxFD = count( $this->clientFD );
		for( $i = 0; $i < $maxFD; $i++ )
		$this->closeConnection( $i );

		@socket_close( $this->initFD );

		$this->sendDebugMessage( "Shutdown server." );
		exit;
	}

	/**
	 * get current amount of clients
	 *
	 * @access public
	 * @return int $clients amount of clients
	 */
	function getClients(){
		return $this->clients;
	}

	/**
	 * send data to a client
	 *
	 * @access public
	 * @param int   $clientId ID of the client
	 * @param string $data   data to send
	 * @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message
	 */
	function sendData( $clientId, $data, $debugData = true ){
		if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
		return false;

		if( $debugData )
		$this->sendDebugMessage( "sending: \"" . $data . "\" to: $clientId" );

		if(!@socket_write( $this->clientFD[$clientId], $data ) )
		$this->sendDebugMessage( "Could not write '".$data."' client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
	}

	/**
	 * send data to all clients
	 *
	 * @access public
	 * @param string $data   data to send
	 * @param array $exclude client ids to exclude
	 */
	function broadcastData( $data, $exclude = array(), $debugData = true ){
		if( !empty( $exclude ) && !is_array( $exclude ) )
		$exclude = array( $exclude );
		for( $i = 0; $i < count( $this->clientFD ); $i++ ){
			if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $exclude ) ){
				if( $debugData )
				$this->sendDebugMessage( "sending: \"" . $data . "\" to: $i" );

				if(!@socket_write( $this->clientFD[$i], $data ) )
				$this->sendDebugMessage( "Could not write '".$data."' client ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] )." )." );
			}
		}
	}

	/*========================== edit by riidt start =============================*/
	//向特定的组发信息 group中存放的是对应的socket id
	function broadcastGroup( $data, $group = array(), $exclude = array(), $debugData = true ){
		if(!empty( $group ))
		{
			foreach($group as $i)
			{
				if(in_array( $i, $exclude ))
				continue;
				$skt = $this->clientFD[$i];
				if( isset( $skt ) && $skt != NULL)
				{
					if( $debugData )
					$this->sendDebugMessage( "sending: \"" . $data . "\" to: ".$i );

					if(!@socket_write( $skt, $data."\n" ) )
					$this->sendDebugMessage( "Could not write '".$data."' client ".$i." ( ".$this->getLastSocketError( $skt )." )." );
				}
			}
		}
	}
	//连接断开后清除掉对应的人的游戏信息
	function onClose($id)
	{
		$u = $this->userCenter->userList[$id];
		$response = new Response();
		$response->event = "USER_EXIT";
		$response->body = $u;
		$room = $this->userCenter->roomList[$u->roomID];
		foreach($room->userList as $user)
		{
			if($this->userCenter->userList[$user]->tableID==="")
			{
				//不在游戏桌
				$this->sendData($user,$response);
			}
		}

		//如果玩家当前已进入游戏桌
		$ul=array();
		if($u->seatID!==""){
			unset($this->userCenter->roomList[$u->roomID]->tableList[$u->tableID]->seatList[$u->seatID]);
			foreach($this->userCenter->roomList[$u->roomID]->tableList[$u->tableID]->seatList as $seat)
			{
				array_push($ul,$seat->userID);
			}
			$this->broadcastGroup($response,$ul);
			$this->sendDebugMessage("seat delete ok");
		}
		//清除玩家在桌位的信息
		if($u->tableID!=="")
		{
			//如果该玩家是当前桌位的最后一个玩家
			if(count($this->userCenter->roomList[$u->roomID]->tableList[$u->tableID]->seatList)==0)
			{
				$response = new Response();
				$response->event = "DESK_DELETE";
				$response->body = $this->userCenter->roomList[$u->roomID]->tableList[$u->tableID];
					
				foreach($room->userList as $user)
				{
					if($this->userCenter->userList[$user]->tableID==="")
					{
						//不在游戏桌
						$this->sendData($user,$response);
					}
				}
					
				unset($this->userCenter->roomList[$u->roomID]->tableList[$u->tableID]);
				$this->sendDebugMessage("desk delete ok");
			}
		}
		//清除玩家在该房间内的信息
		if($u->roomID!==""){
			unset($this->userCenter->roomList[$u->roomID]->userList[$id]);
			$this->sendDebugMessage("room user info delete ok");
		}
		unset($this->userCenter->userList[$id]);
		
		print_r($this->userCenter);
		
		$this->sendDebugMessage("server user info delete ok");
	}
	/*========================== edit by riidt end =============================*/

	/**
	 * get current information about a client
	 *
	 * @access public
	 * @param int   $clientId ID of the client
	 * @return array $info   information about the client
	 */
	function getClientInfo( $clientId ){
		if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
		return false;
		return $this->clientInfo[$clientId];
	}

	/**
	 * send a debug message
	 *
	 * @access private
	 * @param string $msg message to debug
	 */
	function sendDebugMessage( $msg ){
		if( !$this->debug )
		return false;

		$msg = date( "Y-m-d H:i:s", time() ) . " " . $msg;

		switch( $this->debugMode ){
			case "text":
				$msg = $msg."\n";
				break;
			case "html":
				$msg = htmlspecialchars( $msg ) . "<br />\n";
				break;
		}

		if( $this->debugDest == "stdout" || empty( $this->debugDest ) ){
			echo "\n$msg\n";
			flush();
			return true;
		}

		error_log( $msg, 3, $this->debugDest );
		return true;
	}

	/**
	 * return string for last socket error
	 *
	 * @access public
	 * @return string $error last error
	 */
	function getLastSocketError( &$fd ){
		$lastError = socket_last_error( $fd );
		return "msg: " . socket_strerror( $lastError ) . " / Code: ".$lastError;
	}
	function onReceiveData($ip,$data){
		//$this->broadcastData( $data,array(), true );
		/*========================== edit by riidt �� =============================*/
		$data = json_decode($data);
		include "logic/".$data->action.".php";

		/*========================== edit by riidt �� =============================*/

	}
}

$patServer = new patServer();
$patServer->start();

?>